package rad.framework.mda.ejb3gen.actions;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import rad.framework.mda.ejb3gen.GeneratorPlugin;
import rad.framework.mda.ejb3gen.views.EntityListSelectionDialog;

import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.ui.provider.ExtendedImageRegistry;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.util.TransactionUtil;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.dialogs.SelectionDialog;
import org.eclipse.ui.navigator.CommonActionProvider;
import org.eclipse.ui.navigator.ICommonActionExtensionSite;
import org.eclipse.ui.navigator.ICommonMenuConstants;
import org.eclipse.uml2.common.edit.command.ChangeCommand;
import org.eclipse.uml2.uml.Comment;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.LiteralUnlimitedNatural;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Parameter;
import org.eclipse.uml2.uml.ParameterDirectionKind;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.Type;

/**
 * 
 * @author Robert
 * 
 * Provide an interface with the four CRUD operations: save, get, query and
 * delete.
 */
public class ApplyCrudPatternActionExtension extends CommonActionProvider
		implements IMenuListener {

	private ICommonActionExtensionSite aConfig;

	@Override
	public void init(ICommonActionExtensionSite aConfig) {
		this.aConfig = aConfig;
	}

	public void fillContextMenu(IMenuManager aMenu) {
		aMenu.appendToGroup(ICommonMenuConstants.GROUP_NEW, new CrudAction());
	}

	private void createCrudOperations() {
		Type entityType = getEntityType();
		if (entityType == null)
			return;
		Type longType = getLongType();
		Type stringType = getStringType();
		createSaveOperation(entityType);
		createGetOperation(entityType, longType);
		createQueryOperation(entityType, longType, stringType);
		createDeleteOperation(entityType);
	}

	private Type getStringType() {
		Package pkg = getInterface().getModel();
		Profile types = pkg.getAppliedProfile("types");
		return types.getOwnedType("String");
	}

	private Type getLongType() {
		Package pkg = getInterface().getModel();
		Profile types = pkg.getAppliedProfile("types");
		return types.getOwnedType("Long");
	}

	private void createSaveOperation(Type entityType) {
		String name = lowerName(entityType);
		String comment = "Insert or update " + name + ".";
		Interface service = getInterface();
		Operation operation = service.createOwnedOperation("save", null, null);
		addComment(operation, comment);
		applyStereotype(operation, "javax.jws.WebMethod");
		Parameter parameter = addOutParameter(operation, "return", entityType,
				1, 1);
		addComment(parameter, "returns " + name
				+ " with value of property id entered.");
		parameter = addInParameter(operation, name, entityType);
		addComment(parameter, name + " to insert or update.");
	}

	private Parameter addOutParameter(Operation operation, String name,
			Type type, int lower, int upper) {
		Parameter parameter = operation.createOwnedParameter(name, type);
		parameter.setDirection(ParameterDirectionKind.RETURN_LITERAL);
		parameter.setLower(lower);
		parameter.setUpper(upper);
		return parameter;
	}

	private Parameter addInParameter(Operation operation, String name, Type type) {
		Parameter parameter = operation.createOwnedParameter(name, type);
		Stereotype stereotype = applyStereotype(parameter, "javax.jws.WebParam");
		parameter.setValue(stereotype, "name", name);
		return parameter;
	}

	private void addComment(Element element, String comment) {
		Comment newcomment = element.createOwnedComment();
		newcomment.setBody(comment);

	}

	private String lowerName(Type type) {
		StringBuffer result;
		if (type == null) {
			result = new StringBuffer("unknown");
		} else {
			result = new StringBuffer(type.getName());
			char ch = result.charAt(0);
			ch = Character.toLowerCase(ch);
			result.setCharAt(0, ch);
		}
		return result.toString();
	}

	private Stereotype applyStereotype(Element element, String stereoptypeName) {
		Stereotype result = null;
		for (Iterator it = element.getApplicableStereotypes().iterator(); it
				.hasNext();) {
			Stereotype stereotype = (Stereotype) it.next();
			if (stereotype.getName().equals(stereoptypeName)) {
				element.applyStereotype(stereotype);
				result = stereotype;
			}

		}
		return result;
	}

	private Interface getInterface() {
		return (Interface) getSelection().getFirstElement();
	}

	private void createGetOperation(Type entityType, Type longType) {
		String name = lowerName(entityType);
		String comment = "Retrieve the " + name + " with given id.";
		Interface service = getInterface();
		Operation operation = service.createOwnedOperation("get", null, null);
		addComment(operation, comment);
		applyStereotype(operation, "javax.jws.WebMethod");
		Parameter parameter = addOutParameter(operation, "return", entityType,
				1, 1);
		parameter.setDirection(ParameterDirectionKind.RETURN_LITERAL);
		addComment(parameter, "returns retrieved " + name + ".");
		parameter = addInParameter(operation, "id", longType);
		addComment(parameter, "id of " + name + " to retrieve.");
	}

	private void createDeleteOperation(Type entityType) {
		String name = lowerName(entityType);
		String comment = "Delete the " + name + ".";
		Interface service = getInterface();
		Operation operation = service
				.createOwnedOperation("delete", null, null);
		addComment(operation, comment);
		applyStereotype(operation, "javax.jws.WebMethod");
		Parameter parameter = addOutParameter(operation, "return", entityType,
				1, 1);
		parameter.setDirection(ParameterDirectionKind.RETURN_LITERAL);
		addComment(parameter, "returns deleted " + name + ".");
		parameter = addInParameter(operation, name, entityType);
		addComment(parameter, name + " to remove.");

	}

	private void createQueryOperation(Type entityType, Type longType,
			Type stringType) {
		Operation operation;
		Parameter parameter;
		String name = lowerName(entityType);
		String pluralName = pluralName(name);
		String comment1 = "Retrieve all " + pluralName + " matching criteria.";
		// String comment2 = "This is a Query By Example (QBE).";
		Interface service = getInterface();
		operation = service.createOwnedOperation("query", null, null);
		addComment(operation, comment1);
		// addComment(operation, comment2);
		parameter = addInParameter(operation, "example", entityType);
		addComment(parameter, "specifies criteria to match.");
		parameter = addInParameter(operation, "first", longType);
		addComment(
				parameter,
				"if several entities match criteria, begin output list with the one at this index.");
		parameter = addInParameter(operation, "max", longType);
		addComment(
				parameter,
				"if several entities match criteria, return output list with at most  this many elements.");
		parameter = addInParameter(operation, "orderBy", stringType);
		addComment(parameter,
				"order elements in output list by property with this name.");
		parameter = addOutParameter(operation, "return", entityType, 0,
				LiteralUnlimitedNatural.UNLIMITED);
		parameter.setDirection(ParameterDirectionKind.RETURN_LITERAL);
		applyStereotype(operation, "javax.jws.WebMethod");
		addComment(parameter, "Collection of all " + name
				+ "s matching criteria.");
	}

	/**
	 * 
	 * This method uses a primitive algorithm that can't handle the vagaries of
	 * the English language. Out of 'child' it will make 'childs'.
	 */
	private String pluralName(String name) {
		String result;
		if (name.endsWith("s")) {
			result = name + "es";
		} else {
			result = name + "s";
		}
		return result;
	}

	// Ask user to choose entity to be managed.
	private Type getEntityType() {
		Type result = null;
		SelectionDialog edialog = getEntitySelectionDialog();
		edialog.open();
		Object[] r = edialog.getResult();
		if (r != null && r.length > 0) {
			result = (Type) r[0];
		}
		return result;
	}

	private SelectionDialog getEntitySelectionDialog() {
		return new EntityListSelectionDialog(Display.getDefault()
				.getActiveShell(), getEntityList(getInterface().getModel(),
				new ArrayList()), new EntityLabelProvider());
	}

	@SuppressWarnings("unchecked")
	private List getEntityList(Package pkg, List result) {
		for (Iterator it = pkg.getOwnedElements().iterator(); it.hasNext();) {
			Element element = (Element) it.next();
			if (isEntity(element)) {
				result.add(element);
			} else if (element instanceof Package) {
				getEntityList((Package) element, result);
			}
		}
		return result;
	}

	private boolean isEntity(Element element) {
		return (element instanceof org.eclipse.uml2.uml.Class);
//		boolean result = false;
//		for (Iterator it = element.getAppliedStereotypes().iterator(); it
//				.hasNext();) {
//			Stereotype stereotype = (Stereotype) it.next();
//			if (stereotype.getName().equals("javax.persistence.Entity")) {
//				result = true;
//			}
//		}
//		return result;
	}

	public void menuAboutToShow(IMenuManager manager) {
	}

	private IStructuredSelection getSelection() {
		return (IStructuredSelection) getContext().getSelection();
	}

	private class CrudAction extends Action {
		private static final String LABEL = "Apply Crud Pattern";

		public CrudAction() {
			setText(LABEL);
			setToolTipText(getText() + " Tip");
			setDescription(getText() + " Description");
		}

		public void run() {
			// needed; otherwise an EMF transaction error occurs.
			getEditingDomain().getCommandStack().execute(
					new RefreshingChangeCommand(getEditingDomain(),
							new Runnable() {
								public void run() {
									createCrudOperations();
								}
							}, LABEL));
		}

		private TransactionalEditingDomain getEditingDomain() {
			return TransactionUtil.getEditingDomain(getInterface());
		}

		private Interface getInterface() {
			return (Interface) getSelection().getFirstElement();
		}
	}

	private class RefreshingChangeCommand extends ChangeCommand {
		public RefreshingChangeCommand(EditingDomain editingDomain,
				Runnable runnable, String label) {
			super(editingDomain, runnable, label);
		}

		public void execute() {
			super.execute();
			getViewer().refresh();
		}

		private TreeViewer getViewer() {
			return (TreeViewer) aConfig.getStructuredViewer();
		}

		public void undo() {
			super.undo();
			getViewer().refresh();
		}

		public void redo() {
			super.redo();
			getViewer().refresh();
		}

	}

	public class EntityLabelProvider implements ILabelProvider {

		public Image getImage(Object element) {
			Object u = getAdapter(element).getImage(element);
			return ExtendedImageRegistry.getInstance().getImage(u);
		}

		public String getText(Object element) {
			return getAdapter(element).getText(element);
		}

		public void addListener(ILabelProviderListener listener) {
		}

		public void dispose() {
		}

		public boolean isLabelProperty(Object element, String property) {
			return false;
		}

		public void removeListener(ILabelProviderListener listener) {
		}

		protected IItemLabelProvider getAdapter(Object element) {
			return (IItemLabelProvider) GeneratorPlugin.getDefault()
					.getItemProvidersAdapterFactory().adapt(element,
							IItemLabelProvider.class);
		}
	}
}
